package com.ld.shieldsb.minio.service;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import io.minio.BucketExistsArgs;
import io.minio.DownloadObjectArgs;
import io.minio.GetBucketPolicyArgs;
import io.minio.GetObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.ListObjectsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveBucketArgs;
import io.minio.RemoveObjectArgs;
import io.minio.RemoveObjectsArgs;
import io.minio.Result;
import io.minio.SetBucketPolicyArgs;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.UploadObjectArgs;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;

/**
 * 
 * Minio上传服务
 * 
 * @author <a href="mailto:550827627@qq.com" target="_blank">刘金浩</a>
 * @date 2020-12-14 15:00:56
 *
 */
@Component
public class MinioUploadService {

    @Autowired
    private MinioClient minioClient;

    private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 3600; // 过期时间7天，单位秒

    /**
     * 
     * 检查存储桶（可理解为目录）是否存在
     * 
     * @Title bucketExists
     * @author 刘金浩
     * @date 2021年4月1日 上午11:26:46
     * @param bucketName
     * @return boolean
     */
    @SneakyThrows // 异常抛出
    public boolean bucketExists(String bucketName) {
        return bucketExists(bucketName, true);
    }

    /**
     * 
     * 检查存储桶（可理解为目录）是否存在，不存在则创建
     * 
     * @Title bucketExists
     * @author 刘金浩
     * @date 2021年4月1日 上午11:26:46
     * @param bucketName
     * @param create
     * @return boolean
     */
    @SneakyThrows
    public boolean bucketExists(String bucketName, boolean create) {

        if (create) {
            return makeBucket(bucketName);
        } else {
            boolean flag = false;
            flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (flag) {
                return true;
            }
            return false;
        }
    }

    /**
     * 
     * 创建存储桶
     * 
     * @Title makeBucket
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @return boolean
     */
    public boolean makeBucket(String bucketName) {
        return makeBucket(bucketName, true);
    }

    /**
     * 
     * 创建存储桶
     * 
     * @Title makeBucket
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param publicBucket
     *            是否公开
     * @return boolean
     */
    public boolean makeBucket(String bucketName, boolean publicBucket) {
        try {

            boolean flag = false;
            flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (flag) {
                return true;
            } else {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                if (publicBucket) {
                    setBucketPolicy(bucketName);
                }
                return true;
            }
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 
     * 设置存储桶的策略
     * 
     * @Title setBucketPolicy
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @return void
     */
    @SneakyThrows
    public void setBucketPolicy(String bucketName) {
        /*StringBuilder builder = new StringBuilder();
        builder.append("{\n");
        builder.append("    \"Statement\": [\n");
        builder.append("        {\n");
        builder.append("            \"Action\": [\n");
        builder.append("                \"s3:GetBucketLocation\",\n");
        builder.append("                \"s3:ListBucket\"\n");
        builder.append("            ],\n");
        builder.append("            \"Effect\": \"Allow\",\n");
        builder.append("            \"Principal\": {\n");
        builder.append("            \"AWS\": [\"*\"] \n");
        builder.append("            },\n");
        builder.append("            \"Resource\": \"arn:aws:s3:::" + bucketName + "\"\n");
        builder.append("        },\n");
        builder.append("        {\n");
        builder.append("            \"Action\": \"s3:GetObject\",\n");
        builder.append("            \"Effect\": \"Allow\",\n");
        builder.append("            \"Principal\": {\n");
        builder.append("            \"AWS\": [\"*\"] \n");
        builder.append("            },\n");
        builder.append("            \"Resource\": \"arn:aws:s3:::" + bucketName + "/*\"\n");
        builder.append("        }\n");
        builder.append("    ],\n");
        builder.append("    \"Version\": \"2012-10-17\"\n");
        builder.append("}\n");*/

        Map<String, Object> dataMap = new LinkedHashMap<>();

        List<Map<String, Object>> statementList = new ArrayList<>();
        dataMap.put("Statement", statementList);

        Map<String, Object> dataListMap = new LinkedHashMap<>();
        dataListMap.put("Effect", "Allow");
        dataListMap.put("Principal", ImmutableMap.of("AWS", ImmutableList.of("*")));
        dataListMap.put("Resource", ImmutableList.of("arn:aws:s3:::" + bucketName));
        dataListMap.put("Action", ImmutableList.of("s3:GetBucketLocation", "s3:ListBucket"));
        statementList.add(dataListMap);

        Map<String, Object> dataListMap2 = new LinkedHashMap<>();
        dataListMap2.put("Effect", "Allow");
        dataListMap2.put("Principal", ImmutableMap.of("AWS", ImmutableList.of("*")));
        dataListMap2.put("Resource", ImmutableList.of("arn:aws:s3:::" + bucketName + "/*"));
        dataListMap2.put("Action", ImmutableList.of("s3:GetObject"));
        statementList.add(dataListMap2);

        dataMap.put("Version", "2012-10-17");

        String configStr = JSON.toJSONString(dataMap);
        // System.out.println(configStr);
        // System.out.println(builder.toString());
        minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(configStr).build());
    }

    /**
     * 
     * 列出存储桶的策略设置
     * 
     * @Title getBucketPolicy
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @return String
     */
    @SneakyThrows
    public String getBucketPolicy(String bucketName) {
        String config = minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());
        return config;
    }

    /**
     * 
     * 列出所有存储桶名称
     * 
     * @Title listBucketNames
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @return List<String>
     */
    @SneakyThrows
    public List<String> listBucketNames() {
        List<Bucket> bucketList = listBuckets();
        List<String> bucketListName = new ArrayList<>();
        for (Bucket bucket : bucketList) {
            bucketListName.add(bucket.name());
        }
        return bucketListName;
    }

    /**
     * 
     * 列出所有存储桶
     * 
     * @Title listBuckets
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @return List<Bucket>
     */
    @SneakyThrows
    public List<Bucket> listBuckets() {
        return minioClient.listBuckets();
    }

    /**
     * 
     * 删除存储桶
     * 
     * @Title removeBucket
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @return boolean
     */
    @SneakyThrows
    public boolean removeBucket(String bucketName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            Iterable<Result<Item>> myObjects = listObjects(bucketName);
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                // 有对象文件，则删除失败
                if (item.size() > 0) {
                    return false;
                }
            }
            // 删除存储桶，注意，只有存储桶为空时才能删除成功。
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
            flag = bucketExists(bucketName);
            if (!flag) {
                return true;
            }

        }
        return false;
    }

    /**
     * 
     * 列出存储桶中的所有对象名称
     * 
     * @Title listObjectNames
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @return List<String>
     */
    @SneakyThrows
    public List<String> listObjectNames(String bucketName) {
        List<String> listObjectNames = new ArrayList<>();
        boolean flag = bucketExists(bucketName);
        if (flag) {
            Iterable<Result<Item>> myObjects = listObjects(bucketName);
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                listObjectNames.add(item.objectName());
            }
        }
        return listObjectNames;
    }

    /**
     * 
     * 列出存储桶中的所有对象
     * 
     * @Title listObjects
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @return Iterable<Result<Item>>
     */
    @SneakyThrows
    public Iterable<Result<Item>> listObjects(String bucketName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
        }
        return null;
    }

    /**
     * 
     * 上传文件(通过路径)
     * 
     * @Title uploadObject
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectName
     *            存储后的对象名称
     * @param filePath
     *            文件路径
     * @return StatObjectResponse
     */
    @SneakyThrows
    public StatObjectResponse uploadObject(String bucketName, String objectName, String filePath) {
        boolean flag = bucketExists(bucketName);
        StatObjectResponse statObject = null;
        if (flag) {
            minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(filePath).build());
            statObject = statObject(bucketName, objectName);
        }
        return statObject;
    }

    /**
     * 上传文件（通过MultipartFile）
     * 
     * @Title putObject
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectName
     *            存储后的对象名称
     * @param file
     *            文件对象
     * @return StatObjectResponse
     */
    @SneakyThrows
    public StatObjectResponse putObject(String bucketName, String objectName, MultipartFile file) {
        boolean flag = bucketExists(bucketName);
        StatObjectResponse statObject = null;
        if (flag) {
            minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build());
            statObject = statObject(bucketName, objectName);
        }
        return statObject;

    }

    /**
     * 
     * 以流的形式获取一个文件对象
     * 
     * @Title getObject
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectName
     *            存储后的对象名称
     * @return InputStream
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            StatObjectResponse statObject = statObject(bucketName, objectName);
            if (statObject != null && statObject.size() > 0) {
                InputStream stream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
                return stream;
            }
        }
        return null;
    }

    /**
     * 
     * 以流的形式获取一个文件对象（断点下载）
     * 
     * @Title getObject
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectName
     *            存储桶里的对象名称
     * @param offset
     *            起始字节的位置
     * @param length
     *            要读取的长度 (可选，如果无值则代表读到文件结尾)
     * @return InputStream
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName, long offset, Long length) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            StatObjectResponse statObject = statObject(bucketName, objectName);
            if (statObject != null && statObject.size() > 0) {
                InputStream stream = minioClient
                        .getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build());
                return stream;
            }
        }
        return null;
    }

    /**
     * 
     * 下载并将文件保存到本地
     * 
     * @Title getObject
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectName
     *            存储的对象名称
     * @param fileName
     *            保存路径
     * @return boolean
     */
    @SneakyThrows
    public boolean getObject(String bucketName, String objectName, String fileName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            StatObjectResponse statObject = statObject(bucketName, objectName);
            if (statObject != null && statObject.size() > 0) {
                minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
                return true;
            }
        }
        return false;
    }

    /**
     * 
     * 删除一个对象(即文件)
     * 
     * @Title removeObject
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectName
     *            存储的对象名称
     * @return boolean
     */
    @SneakyThrows
    public boolean removeObject(String bucketName, String objectName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
            return true;
        }
        return false;
    }

    /**
     * 
     * 删除指定桶的多个文件对象,返回删除错误的对象列表，全部删除成功，返回空列表
     * 
     * @Title removeObject
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectNames
     *            含有要删除的多个object名称的迭代器对象
     * @return List<String>
     */
    @SneakyThrows
    public List<String> removeObject(String bucketName, List<DeleteObject> objectNames) {
        List<String> deleteErrorNames = new ArrayList<>();
        boolean flag = bucketExists(bucketName);
        if (flag) {

            Iterable<Result<DeleteError>> results = minioClient
                    .removeObjects(RemoveObjectsArgs.builder().bucket("my-bucketname").objects(objectNames).build());
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                deleteErrorNames.add(error.objectName());
            }

        }
        return deleteErrorNames;
    }

    /**
     * 
     * 获取对象的元数据
     * 
     * @Title statObject
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectName
     *            存储的对象名称
     * @return StatObjectResponse
     */
    @SneakyThrows
    public StatObjectResponse statObject(String bucketName, String objectName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            StatObjectResponse statObject = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
            return statObject;
        }
        return null;
    }

    /**
     * 
     * 生成一个给HTTP GET请求用的presigned URL。 浏览器/移动端的客户端可以用这个URL进行下载，即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间，默认值是7天。
     * 
     * @Title getObjectUrl
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectName
     *            存储的对象名称
     * @return String
     */
    @SneakyThrows
    public String getObjectUrl(String bucketName, String objectName) {
        return getObjectUrl(bucketName, objectName, DEFAULT_EXPIRY_TIME);
    }

    /**
     * 
     * 生成一个给HTTP GET请求用的presigned URL。 浏览器/移动端的客户端可以用这个URL进行下载，即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间，默认值是7天。
     * 
     * @Title getObjectUrl
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectName
     *            存储的对象名称
     * @param expiry
     *            失效时间（以秒为单位），默认是7天，不得大于七天
     * @return String
     */
    @SneakyThrows
    public String getObjectUrl(String bucketName, String objectName, Integer expiry) {
        boolean flag = bucketExists(bucketName);
        String url = "";
        if (flag) {
            if (expiry != null) {
                if (expiry < 0 || expiry > DEFAULT_EXPIRY_TIME) {
                    expiry = DEFAULT_EXPIRY_TIME;
                }
            } else {
                expiry = DEFAULT_EXPIRY_TIME;
            }
            url = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(objectName).expiry(expiry).build());
        }
        return url;
    }

    /**
     * 
     * 获取文件到response流，注意流未关闭
     * 
     * @Title getObjectResponse
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectName
     *            存储的对象名称
     * @param response
     *            输出流
     * @return void
     */
    @SneakyThrows
    public void getObjectResponse(String bucketName, String objectname, HttpServletResponse response) {
        InputStream stream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectname).build());
        OutputStream out = null;
        out = response.getOutputStream();
        int len = 0;
        byte[] b = new byte[1024];
        while ((len = stream.read(b)) != -1) {
            out.write(b, 0, len);
        }
        out.flush();
    }

    /**
     * 
     * 下载文件到本地
     * 
     * @Title downloadFile
     * @author 刘金浩
     * @date 2020-12-14 15:00:56
     * @param bucketName
     *            存储桶名称
     * @param objectName
     *            存储的对象名称
     * @param fileName
     * @return void
     */
    @SneakyThrows
    public void downloadFile(String bucketName, String objectname, String filePath) {
        minioClient.downloadObject(DownloadObjectArgs.builder().bucket(bucketName).object(objectname).filename(filePath).build());
    }

    /**
     * 获取客户端
     * 
     * @Title getClient
     * @author 吕凯
     * @date 2021年4月1日 下午2:39:48
     * @return MinioClient
     */
    public MinioClient getClient() {
        return minioClient;
    }

}
